package net.kleinhenz.droidshop.xml;

import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import net.kleinhenz.droidshop.DroidShop;
import net.kleinhenz.droidshop.model.Price;
import net.kleinhenz.droidshop.model.Product;
import net.kleinhenz.droidshop.model.Review;

import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;

import android.util.Log;

public class DroidShopXMLHandler extends DefaultHandler
{	
	// temporary objects
	private List<Product> productList = null;
	private Product product = null;
	private Review review = null;
	private Price price = null;
	
	// content
	private StringBuffer contentBuffer = new StringBuffer();
	
	// root element
	private boolean inProductList = false;
	private boolean inProduct = false;
	
	// top level elements
	private boolean inName = false;
	private boolean inType = false;
	private boolean inAsin = false;
	private boolean inEan = false;
	private boolean inAuthor = false;
	private boolean inDetaillink = false;
	private boolean inDescription = false;	
	private boolean inStars = false;
	private boolean inImagelink = false;
	private boolean inReviews = false;
	private boolean inPrices = false;

	// second level: reviews
	private boolean inReview = false;

	// second level: prices
	private boolean inPrice = false;

	// third level: review
	private boolean inReviewTitle = false;	
	private boolean inReviewText = false;	
	private boolean inReviewStars = false;	

	// third level: price
	private boolean inPriceFormattedPrice = false;	
	private boolean inPriceMerchant = false;	
	private boolean inPriceAvailability = false;	
	private boolean inPriceBuyLink = false;	

	@Override
	public void startElement(String uri, String localName, String name,
			Attributes attributes) throws SAXException
	{
		String tName = localName.trim();
		// root element
		if (tName.equals("productList")) 
		{
			inProductList = true;
		}
		// top level elements
		if (tName.equals("product"))
		{
			if (productList == null)
				productList = new ArrayList<Product>();				

			inProduct = true;
			product = new Product();
		}
		// second level elements: product
		else if (inProduct && tName.equals("name"))
		{
			inName = true;
			emptyContentBuffer();
		}
		else if (inProduct && tName.equals("type"))
		{
			inType = true;
			emptyContentBuffer();
		}
		else if (inProduct && tName.equals("asin"))
		{
			inAsin = true;
			emptyContentBuffer();
		}
		else if (inProduct && tName.equals("ean"))
		{
			inEan = true;
			emptyContentBuffer();
		}
		else if (inProduct && tName.equals("author"))
		{
			inAuthor = true;
			emptyContentBuffer();
		}
		else if (inProduct && tName.equals("detaillink"))
		{
			inDetaillink = true;
			emptyContentBuffer();
		}
		else if (inProduct && tName.equals("description"))
		{
			inDescription = true;
			emptyContentBuffer();
		}
		else if (inProduct && !inReview && tName.equals("stars"))
		{
			inStars = true;
			emptyContentBuffer();
		}
		else if (inProduct && tName.equals("imagelink"))
		{
			inImagelink = true;
			emptyContentBuffer();
		}
		else if (inProduct && tName.equals("reviews"))
		{
			inReviews = true;
		}
		else if (inProduct && tName.equals("prices"))
		{
			inPrices = true;
		}
		// second level: reviews
		else if (inReviews && tName.equals("review"))
		{
			inReview = true;
			review = new Review();
		}
		else if (inReview && tName.equals("title"))
		{
			inReviewTitle = true;
			emptyContentBuffer();
		}
		else if (inReview && tName.equals("text"))
		{
			inReviewText = true;
			emptyContentBuffer();
		}
		else if (inReview && tName.equals("stars"))
		{
			inReviewStars = true;
			emptyContentBuffer();
		}
		// second level: prices
		else if (inPrices && tName.equals("price"))
		{
			inPrice = true;
			price = new Price();
		}
		else if (inPrice && tName.equals("formattedprice"))
		{
			inPriceFormattedPrice = true;
			emptyContentBuffer();
		}
		else if (inPrice && tName.equals("merchant"))
		{
			inPriceMerchant = true;
			emptyContentBuffer();
		}
		else if (inPrice && tName.equals("availability"))
		{
			inPriceAvailability = true;
			emptyContentBuffer();
		}
		else if (inPrice && tName.equals("buylink"))
		{
			inPriceBuyLink = true;
			emptyContentBuffer();
		}
	}
	
	@Override
	public void endElement(String uri, String localName, String name)
			throws SAXException
	{
		String tName = localName.trim();
		// root element
		if (inProductList && tName.equals("productList"))
			inProductList = false;
		// top level elements
		if (inProduct && tName.equals("product"))
		{
			inProduct = false;
			productList.add(product);
			product = null;
		}
		else if (inReviews && tName.equals("reviews"))
			inReviews = false;
		else if (inPrices && tName.equals("prices"))
			inPrices = false;
		else if (inName && tName.equals("name"))
		{
			inName = false;
			product.setName(contentBuffer.toString().trim().replaceAll("\\n", "").replaceAll("\\t", ""));
		}
		else if (inType && tName.equals("type"))
		{
			inType = false;
			product.setType(contentBuffer.toString().trim());
		}
		else if (inAsin && tName.equals("asin"))
		{
			inAsin = false;
			product.setAsin(contentBuffer.toString().trim());
		}
		else if (inEan && tName.equals("ean"))
		{
			inEan = false;
			product.setEan(contentBuffer.toString().trim());
		}
		else if (inAuthor && tName.equals("author"))
		{
			inAuthor = false;
			product.setAuthor(contentBuffer.toString().trim());
		}
		else if (inDetaillink && tName.equals("detaillink"))
		{
			inDetaillink = false;
			if (contentBuffer.toString().trim()!=null && !contentBuffer.toString().trim().equals(""))
				try 
				{
					product.setDetailLink(new URL(contentBuffer.toString().trim()));
				} 
				catch (MalformedURLException e) 
				{
					Log.d(DroidShop.APP_NAME, "DroidShopXML: Error: not an URL: " + contentBuffer.toString().trim());
					product.setDetailLink(null);
				}
		}
		else if (inDescription && tName.equals("description"))
		{
			inDescription = false;
			product.setDescription(contentBuffer.toString().trim().replaceAll("<.*>", ""));
		}
		else if (inStars && tName.equals("stars"))
		{
			inStars = false;
			if (contentBuffer.toString().trim()!=null && !contentBuffer.toString().trim().equals(""))
				product.setStars(Double.parseDouble(contentBuffer.toString().trim()));
		}
		else if (inImagelink && tName.equals("imagelink"))
		{
			inImagelink = false;
			if (contentBuffer.toString().trim()!=null && !contentBuffer.toString().trim().equals(""))
				try
				{
					product.setImageLink(new URL(contentBuffer.toString().trim()));
				} 
				catch (MalformedURLException e) 
				{
					Log.d(DroidShop.APP_NAME, "DroidShopXML: Error: not an URL: " + contentBuffer.toString().trim());
					product.setDetailLink(null);
				}
		}
		else if (inReview && tName.equals("review"))
		{
			inReview = false;
			product.getReviews().add(review);
			review = null;
		}
		else if (inPrice && tName.equals("price"))
		{
			inPrice = false;
			product.getPrices().add(price);
			price = null;
		}
		// second level elements: reviews
		else if (inReviewTitle && tName.equals("title"))
		{
			inReviewTitle = false;
			review.setTitle(contentBuffer.toString().trim());
		}
		else if (inReviewText && tName.equals("text"))
		{
			inReviewText = false;
			review.setText(contentBuffer.toString().trim().replaceAll("<br />", ""));
		}
		else if (inReviewStars && tName.equals("stars"))
		{
			inReviewStars = false;
			if (contentBuffer.toString().trim()!=null && !contentBuffer.toString().trim().equals(""))
				review.setStars(Double.parseDouble(contentBuffer.toString().trim()));
		}
		// second level elements: prices
		else if (inPriceFormattedPrice && tName.equals("formattedprice"))
		{
			inPriceFormattedPrice = false;
			price.setFormattedPrice(contentBuffer.toString().trim().replaceAll("EUR", "€").replaceAll("\\$", "\\$ ").replaceAll("£", "£ "));
		}
		else if (inPriceMerchant && tName.equals("merchant"))
		{
			inPriceMerchant = false;
			price.setMerchant(contentBuffer.toString().trim());
		}
		else if (inPriceAvailability && tName.equals("availability"))
		{
			inPriceAvailability = false;
			price.setAvailability(contentBuffer.toString().trim());
		}		
		else if (inPriceBuyLink && tName.equals("buylink"))
		{
			inPriceBuyLink = false;
			try
			{
				price.setBuyLink(new URL(contentBuffer.toString().trim()));
			} 
			catch (MalformedURLException e) 
			{
				Log.d(DroidShop.APP_NAME, "DroidShopXML: Error: not an URL: " + contentBuffer.toString().trim());
				product.setDetailLink(null);
			}		
		}		
	}
	
	@Override
	public void characters(char[] ch, int start, int length)
			throws SAXException
	{
		if (inContent())
			contentBuffer.append(ch, start, length);
	}
	
	private boolean inContent()
	{
		return (inName || inType || inAsin || inDetaillink || inDescription || inStars || inImagelink || inEan || inAuthor
				|| inReviewTitle || inReviewText || inReviewStars
				|| inPriceFormattedPrice || inPriceMerchant || inPriceAvailability || inPriceBuyLink);
	}

	private void emptyContentBuffer()
	{
		contentBuffer.delete(0, contentBuffer.length());
	}
	
	/**
	 * Reads a DroidShopXML document from the given URL. It returns the
	 * parsed Quest object containing all data read from the DroidShopXML.
	 * 
	 * @param url DroidShopXML document URL.
	 * @return Quest object containing the story.
	 * @throws DroidShopXMLException if parsing fails.
	 */
	public List<Product> readDroidShopXML(URL url) throws DroidShopXMLException 
	{
        try 
        {
            SAXParserFactory spf = SAXParserFactory.newInstance();
            SAXParser sp = spf.newSAXParser();
            XMLReader xr = sp.getXMLReader();
            xr.setContentHandler(this);
            xr.parse(new InputSource(url.openStream()));
        } 
        catch (IOException e) 
        {
            Log.e(DroidShop.APP_NAME, e.toString());
            throw new DroidShopXMLException("I/O error while reading DroidShopXML.", e);
      	} 
        catch (SAXException e) 
      	{
            Log.e(DroidShop.APP_NAME, e.toString());
            throw new DroidShopXMLException("SAX error while reading DroidShopXML.", e);
        } 
        catch (ParserConfigurationException e) 
        {
            Log.e(DroidShop.APP_NAME, e.toString());
            throw new DroidShopXMLException("Parse error while reading DroidShopXML.", e);
        } 
        catch (IllegalArgumentException e) 
        {
        	Log.e(DroidShop.APP_NAME, e.toString());
        	throw new DroidShopXMLException("Parse error while getting type information.", e);
        }
        
        return productList;
    }

	public List<Product> readDroidShopXML(InputStream is) throws DroidShopXMLException 
	{
        try 
        {
            SAXParserFactory spf = SAXParserFactory.newInstance();
            SAXParser sp = spf.newSAXParser();
            XMLReader xr = sp.getXMLReader();
            xr.setContentHandler(this);
            xr.parse(new InputSource(is));
        } 
        catch (IOException e) 
        {
            Log.e(DroidShop.APP_NAME, e.toString());
            throw new DroidShopXMLException("I/O error while reading DroidShopXML.", e);
      	} 
        catch (SAXException e) 
        {
            Log.e(DroidShop.APP_NAME, e.toString());
            throw new DroidShopXMLException("SAX error while reading DroidShopXML.", e);
        } 
        catch (ParserConfigurationException e) 
        {
            Log.e(DroidShop.APP_NAME, e.toString());
            throw new DroidShopXMLException("Parse error while reading DroidShopXML.", e);
        } 
        catch (IllegalArgumentException e) 
        {
        	Log.e(DroidShop.APP_NAME, e.toString());
        	throw new DroidShopXMLException("Parse error while getting type information.", e);
        }
        
        return productList;
    }
}

